/**
 *    Copyright ${license.git.copyrightYears} the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.builder.xml;

import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.ibatis.builder.BaseBuilder;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.CacheRefResolver;
import org.apache.ibatis.builder.IncompleteElementException;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.builder.ResultMapResolver;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.Discriminator;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.mapping.ResultFlag;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

/**
 * @author Clinton Begin
 */
public class XMLMapperBuilder extends BaseBuilder {

  private final XPathParser parser;
  private final MapperBuilderAssistant builderAssistant;
  private final Map<String, XNode> sqlFragments;
  private final String resource;

  @Deprecated
  public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
    this(reader, configuration, resource, sqlFragments);
    this.builderAssistant.setCurrentNamespace(namespace);
  }

  @Deprecated
  public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);
  }

  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
    this(inputStream, configuration, resource, sqlFragments);
    this.builderAssistant.setCurrentNamespace(namespace);
  }

  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);
  }

  private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    super(configuration);
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
    this.parser = parser;
    this.sqlFragments = sqlFragments;
    this.resource = resource;
  }

  public void parse() {
    //判断是否已经加载过该映射文件
    if (!configuration.isResourceLoaded(resource)) {
      // 处理＜mapper＞节点
      configurationElement(parser.evalNode("/mapper"));
      //将 resource 添加到 Configuration.loadedResources 集合中保存, 它是 HashSet<String>
      // 类型的集合，其中记录了已经加载过的映射文件
      configuration.addLoadedResource(resource);
      //注册 Mapper接口
      bindMapperForNamespace();
    }
    //处理 configurationElement()方法中解析失败的<resultMap> 节点
    parsePendingResultMaps();
    //处理 configurationElement()方法中解析失败的<cache-ref> 节点
    parsePendingCacheRefs();
    //处理 configurationElement()方法中解析失败的SQL语句节点
    parsePendingStatements();
  }

  public XNode getSqlFragment(String refid) {
    return sqlFragments.get(refid);
  }

  private void configurationElement(XNode context) {
    try {
      //获取<mapper>的namespace 属性
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      //解析<cache-ref>节点
      cacheRefElement(context.evalNode("cache-ref"));
      //解析＜ cache ＞节点
      cacheElement(context.evalNode("cache"));
      //解析＜parameterMap ＞节点（该节点已废弃，不再推荐使用，不做详细介绍）
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      //解析＜ resultMap ＞节点
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      //解析＜ sql ＞节点
      sqlElement(context.evalNodes("/mapper/sql"));
      //／解析＜ select ＞、＜ insert ＞、＜ update ＞、＜ delete ＞等 SQL 节点
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

  private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

  private void parsePendingResultMaps() {
    Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
    synchronized (incompleteResultMaps) {
      Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
      while (iter.hasNext()) {
        try {
          iter.next().resolve();
          iter.remove();
        } catch (IncompleteElementException e) {
          // ResultMap is still missing a resource...
        }
      }
    }
  }

  private void parsePendingCacheRefs() {
    Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs();
    synchronized (incompleteCacheRefs) {
      Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator();
      while (iter.hasNext()) {
        try {
          iter.next().resolveCacheRef();
          iter.remove();
        } catch (IncompleteElementException e) {
          // Cache ref is still missing a resource...
        }
      }
    }
  }

  private void parsePendingStatements() {
    Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
    synchronized (incompleteStatements) {
      Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
      while (iter.hasNext()) {
        try {
          iter.next().parseStatementNode();
          iter.remove();
        } catch (IncompleteElementException e) {
          // Statement is still missing a resource...
        }
      }
    }
  }

  /**
   * 如果我们希望多个 namespace 共用同一个二级缓存，
   * 即同一个 Cache 对象，则可以使用＜cache-ref>节点进行配置。
   * @param context
   */
  private void cacheRefElement(XNode context) {
    if (context != null) {
      //将当前 Mapper 配置文件的 namespace 与被引用的 Cache 所在的 namespace 之间的对应关单，记录至 Configuration.cacheRefMap 中
      configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
      //创建 CacheRefResolver 对象
      CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
      try {
        //解析 Cache 引用，该过程主要是设置 MapperBuilderAssistant 中的currentCache 和 unresolvedCacheRef字段
        cacheRefResolver.resolveCacheRef();
      } catch (IncompleteElementException e) {
        //／如采解析过程出现异常，则添加到 Configuration.incompleteCacheRefs 集合，主要是在解析cache-ref时，被引用的cache对象还未创建
        configuration.addIncompleteCacheRef(cacheRefResolver);
      }
    }
  }

  /**
   * MyBatis 有非常强大的二级缓存功能， 功能可以非常方便地进行配置， MyBatis 默认情
   * 况下没有开启二级缓存，如果要为某命名空间开启二级缓存功能，则需要在相应映射配置文件
   * 中添加＜ cache＞节点，还可以通过配置＜cache＞节点的相关属性，为二级缓存配置相应的特性 （本
   * 质上就是添加相应的装饰器〉
   * @param context
   * @throws Exception
   */
  private void cacheElement(XNode context) throws Exception {
    if (context != null) {
      // 获取＜ cache ＞节点的 type ）$，性，默认位是 PERPETUAL
      String type = context.getStringAttribute("type", "PERPETUAL");
      //查找 type 属性对应的 Cache 实现，
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      //获取<cache> 节点的 eviction 属性，默认位是 LRU
      String eviction = context.getStringAttribute("eviction", "LRU");
      //解析 eviction 属性，性指定的 Cache 装饰器类型
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      //获取＜ cache ＞节点的 flushInterval 属，性，默认值是 null
      Long flushInterval = context.getLongAttribute("flushInterval");
      //获取＜ cache ＞节点的 size 属性，默认位是 null
      Integer size = context.getIntAttribute("size");
      //获取＜ cache ＞节点的 readOnly 属性，默认值是 false
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      //获取＜ cache ＞节点的 blocking 属性 默认位是 false
      boolean blocking = context.getBooleanAttribute("blocking", false);
      //获取＜cache＞节点下的子节点，将用于初始化二级缓存
      Properties props = context.getChildrenAsProperties();
      //通过 MapperBuilderAssistant 创建 Cache 对象，并添加到 Configuration.caches 集合保存
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

  private void parameterMapElement(List<XNode> list) throws Exception {
    for (XNode parameterMapNode : list) {
      String id = parameterMapNode.getStringAttribute("id");
      String type = parameterMapNode.getStringAttribute("type");
      Class<?> parameterClass = resolveClass(type);
      List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
      List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
      for (XNode parameterNode : parameterNodes) {
        String property = parameterNode.getStringAttribute("property");
        String javaType = parameterNode.getStringAttribute("javaType");
        String jdbcType = parameterNode.getStringAttribute("jdbcType");
        String resultMap = parameterNode.getStringAttribute("resultMap");
        String mode = parameterNode.getStringAttribute("mode");
        String typeHandler = parameterNode.getStringAttribute("typeHandler");
        Integer numericScale = parameterNode.getIntAttribute("numericScale");
        ParameterMode modeEnum = resolveParameterMode(mode);
        Class<?> javaTypeClass = resolveClass(javaType);
        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
        @SuppressWarnings("unchecked")
        Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
        ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
        parameterMappings.add(parameterMapping);
      }
      builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
    }
  }

  /**
   * select 语句查询得到的结果集是一张二维表，水平方向上看是一个个字段，垂直方向上看是
   * 一条条记录, Java 是面向对象的程序设计语言，对象是根据类定义创建的，类之间的引用
   * 系可以认为是嵌套的结构。在 JDBC 编程中，为了将结果集中的数据映射成对象，我们需要自
   * 己写代码从结果集中获取数据，然后封装成对应的对象并设置对象之间的关系，而这些都是大
   * 量的重复性代码。为了减少这些重复的代码， MyBatis 使用 <resultMap></resultMap>节点定义了结果集与结
   * 果对象 JavaBean 对象之间的映射规则，＜resultMap＞节点可以满足绝大部分的映射需求，从
   * 而减少开发人员的重复性劳动，提高开发效率。
   * @param list
   * @throws Exception
   */
  private void resultMapElements(List<XNode> list) throws Exception {
    for (XNode resultMapNode : list) {
      try {
        resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // ignore, it will be retried
      }
    }
  }

  private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
    return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
  }

  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    //获取＜ resultMap ＞的 id 属性，默认值会拼笨所有父节点的 id或value或Property 属性值
    String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
    //javaType和ofType都是用来指定对象类型的，但是JavaType是用来指定pojo中属性的类型，而ofType指定的是映射到list集合属性中pojo的类型。
    //这里的ofType属性只有collection用到
    //javaType不同的result和association用到,association的javaType是一个JavaBean对象
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    //／获取resultMap 节点的 extends 属性，该属性指定了该＜resultMap＞节点 继承关系
    String extend = resultMapNode.getStringAttribute("extends");
    //读取＜ resultMap ＞节点的 autoMapping 属性，将该属性设置为 true ，则启动自动映射功能，
    // 即自动查找与列名同名的属性名，并调用 setter 方法 ，
    // 而设置为 false 后，则需要在 resultMap 节点内明确注明映射关系才会调用对应 setter 方法
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    //解析 type 类型
    Class<?> typeClass = resolveClass(type);
    Discriminator discriminator = null;
    //该集合用于记录解析的结采
    List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
    resultMappings.addAll(additionalResultMappings);
    //处理 resultMap 的子节点
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      //构造器的解析。构造器主要用于没有默认构造器或者有多个构造器的情况
      //处理 constructor 节点
      if ("constructor".equals(resultChild.getName())) {
        processConstructorElement(resultChild, typeClass, resultMappings);
        //discriminator 鉴别器, dtd里面的描述为 discriminator?,只允许出现0次或者1次
      } else if ("discriminator".equals(resultChild.getName())) {
        //处理 discriminator 节点
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        //处理 id 、 result 、 association 、 collection 等节点
        List<ResultFlag> flags = new ArrayList<ResultFlag>();
        if ("id".equals(resultChild.getName())) {
          //如果是＜id＞节点，则向 flags 集合中添加 ResultFlag.ID
          flags.add(ResultFlag.ID);
        }
        //创建 ResultMapping 对象，并添加 resultMappings 集合中保存
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      //创建 ResultMap 对象，并添加到 Configuration.resultMaps 集合中，该集合是 StrictMap 类型
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      //主要是针对resultMap的继承，父ResultMap还未解析的情况
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

  private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
    //获取＜constructor ＞节点的子节点
    List<XNode> argChildren = resultChild.getChildren();
    for (XNode argChild : argChildren) {
      List<ResultFlag> flags = new ArrayList<ResultFlag>();
      //添加 CONSTRUCTOR 标志
      flags.add(ResultFlag.CONSTRUCTOR);
      if ("idArg".equals(argChild.getName())) {
        flags.add(ResultFlag.ID);
      }
      resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
    }
  }

  private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String typeHandler = context.getStringAttribute("typeHandler");
    Class<?> javaTypeClass = resolveClass(javaType);
    @SuppressWarnings("unchecked")
    Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    Map<String, String> discriminatorMap = new HashMap<String, String>();
    for (XNode caseChild : context.getChildren()) {
      String value = caseChild.getStringAttribute("value");
      String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings));
      discriminatorMap.put(value, resultMap);
    }
    return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
  }

  private void sqlElement(List<XNode> list) throws Exception {
    //databaseId用作切换不同的数据源
    if (configuration.getDatabaseId() != null) {
      sqlElement(list, configuration.getDatabaseId());
    }
    sqlElement(list, null);
  }

  private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
    for (XNode context : list) { //遥历＜ sql ＞节点
      //获取 databaseid 属性
      String databaseId = context.getStringAttribute("databaseId");
      //获取id属性
      String id = context.getStringAttribute("id");
      //为 id 添加命名空间
      id = builderAssistant.applyCurrentNamespace(id, false);
      //检测＜ sql ＞的 databaseId 与当前 Configuration 中记录的 databaseId 是否一致
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
        //记录到 XMLMapperBuilder sqlFragments ( Map<String, XNode＞类型）中保存，在
        //XMLMapperBuilder 的构造函数中，可以看到该字段指向了 Configuration.sqlFragments 集合
        sqlFragments.put(id, context);
      }
    }
  }
  
  private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
    if (requiredDatabaseId != null) {
      if (!requiredDatabaseId.equals(databaseId)) {
        return false;
      }
    } else {
      if (databaseId != null) {
        return false;
      }
      // skip this fragment if there is a previous one with a not null databaseId
      if (this.sqlFragments.containsKey(id)) {
        XNode context = this.sqlFragments.get(id);
        if (context.getStringAttribute("databaseId") != null) {
          return false;
        }
      }
    }
    return true;
  }

  private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
    String property;
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
      property = context.getStringAttribute("name");
    } else {
      property = context.getStringAttribute("property");
    }
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String nestedSelect = context.getStringAttribute("select");
    //解析包含的association或collection复合类型, 这些复合类型可以使用外部定义的公用resultMap或者内嵌resultMap
    //如果未指定＜association＞节点的 resultMap 属性，则是匿名的嵌套映射，需妥通过 processNestedResultMappings()方法解析该匿名的嵌套映射
    String nestedResultMap = context.getStringAttribute("resultMap",
        processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
    String notNullColumn = context.getStringAttribute("notNullColumn");
    String columnPrefix = context.getStringAttribute("columnPrefix");
    String typeHandler = context.getStringAttribute("typeHandler");
    String resultSet = context.getStringAttribute("resultSet");
    String foreignColumn = context.getStringAttribute("foreignColumn");
    boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    //解析 javaType, typeHandler, dbcType
    Class<?> javaTypeClass = resolveClass(javaType);
    @SuppressWarnings("unchecked")
    Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    //创建 ResultMapping 对象
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
  }
  
  private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings) throws Exception {
    if ("association".equals(context.getName())
        || "collection".equals(context.getName())
        || "case".equals(context.getName())) {
      //指定 select 性之后，不会生成嵌套的 ResultMap 对象
      //case是不会包含select属性的,如果说association或者collection包含select,就用select属性去查询
      if (context.getStringAttribute("select") == null) {
        //递归解析,resultMap树
        ResultMap resultMap = resultMapElement(context, resultMappings);
        return resultMap.getId();
      }
    }
    return null;
  }

  private void bindMapperForNamespace() {
    //获取映射配文件的命名空间
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        //解析命名空间对应的类型
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) { //是否已经加载了 boundType 接口
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          //追加 namespace 前缀，并添加到 Configuration.loadedResource 中保存
          configuration.addLoadedResource("namespace:" + namespace);
          // 调用MapperRegistry.addMapper() 方法，注册 boundType 接口
          configuration.addMapper(boundType);
        }
      }
    }
  }

}
